程序的本质是什么?数据结构+算法!!!我想这也是很多程序员给出的答案,我自己也认可这一观点,当我们了解了某一门编程语之后,接下来我们面对的往往是数据结构和算法的学习。而现在,我对于程序的本质有了不一样的答案:分化和组合。我的老师曾经告诉我,工程师或者程序员有一个很重要的能力(我猜这也是最重要的能力),那就是发现和解决问题。<!-- More -->而我目前只能接触到解决问题的层次,这也是我也一直在探寻的目标 —— 分化和组合。

分化与组合

在现实的程序开发中,我们碰到的问题一般是一个很大的命题,难以抽象,这时候往往通过分化子问题的方式对程序进行分而治之。在算法里,这种方式叫做分治。

将程序分化成可以抽象的子程序之后,再将他们组合成一个具有完备功能的程序。组合是程序或代码可复用的前提,函数式编程中组合的使用也会给我们带来意向不到的惊喜

Haskell 的组合

Haskell是纯函数式编程语言,他的强大不用我多说,这里展示一下他的组合能力。

compose     :: (b -> c) -> (a -> b) -> a -> c
compose f g = f . g

在函数式编程的组合中,我们是从右到左执行的,上述的例子中我们借助 (.) 函数实现组合,当然,我们也可以用自己的方式实现。

compose       :: (b -> c) -> (a -> b) -> a -> c
compose f g x = f (g x)

通过 Haskell 强大的类型签名中,大致可以推出compose函数的作用。b -> c 代表了一个从b类到c类的映射函数,a -> b也是一样,compose函数接受两个函数fg,返回一个新的函数hh函数表达了从a类到b类再到c类的映射。

举个例子,我们有sum函数 —— 给列表求和,odd函数 —— 判断数字是否是奇数。(简单起见,给他们的类型签名并不准确,但足够简单)

odd      :: Int -> Bool
sum      :: [Int] -> Int

sumIsOdd :: [Int] -> Bool
sumIsOdd = compose odd sum

sumIsOdd [1,2,34,5]

sumIsOdd函数组合了求和与判断奇数两个函数,他并没实现具体的功能,而是通过一种通俗的组合实现了将单一的函数转化成稍微复杂的函数,从而达到功能上的扩充,我想这就是组合的魅力,也是函数式的魅力之一吧。

JavaScript 的组合

JavaScript是一门表现力极强的语言,除了本质上对面向对象的支持,对函数式编程的支持也丝毫不弱,组合函数实现起来虽然复杂,但是也未必不可行,而且相对于Haskell的晦涩,JavaScript更加简单一点

const odd = x => x % 2 !== 0;
const sum = args => args.reduce((a, b) => a + b);

const compose = (...fs) => arg => fs.reduceRight((f, g) => g.call(null, f), arg);

借助ES6的箭头函数实现起来得心应手,可见即便是JavaScript这种以面向对象思想设计的语言也希望在函数式编程上面能有所建树。

顺带一提,对数组扩充的mapfilterreduce等函数,也是JavaScript对函数式编程的支持,虽然只是利用while循环做的语法糖,效率也不如while循环,但是在JavaScript的语境中,很少对效率有极高的要求,所以从语义上来讲,我更倾向用函数式的方式解决纯数据的问题。

小结

函数式编程随着多核CPU的发展,开始再次出现在我们的视野中,有时候也会担心过于吹捧函数式,反而落入俗套。仔细想想,编程范式并不是一枝独秀的世界,而是百家争鸣的,各有擅长的领域。当然在一些环境下两者也是能够共存的,甚至两者同时带来的收益可能更加可观呢。

说回组合,其实我自己也只是一个半吊子,也需要不断的学习才能对组合有一个更加清晰的认识,而不是拘泥于语言,在实际生产中分化和组合也实在太重要了,并不是一两个函数能够概括的,现在的也我只能以这种简单的方式作了解了。


满天星
4 声望2 粉丝

Hello, Bug